"""
本代码是眨眼检测的核心代码。同样直接保存到本地，在自己的项目中引用即可。
代码中不仅对眨眼进行了检测，同时实时计算了当前用户的眨眼频率，对用户3秒前后的眨眼频率进行了比较，若眨眼频率超过限定值，即将告警信息传递到上级UI中进行显示。
（眨眼频率检测代码在112行至127行）
"""
import cv2
import cvzone
import time
from cvzone.FaceMeshModule import FaceMeshDetector  # 导入脸部关键点检测方法
from cvzone.PlotModule import LivePlot  # 导入实时绘图模块
from numpy import double
import tkinter as tk

class PreviousValue:
    def __init__(self):
        self.value = 0

    def update(self, new_value):
        self.value = new_value

    def get(self):
        return self.value


def see_eyes(cap, text):
    # （1）读取视频文件

    # （2）配置
    # 接收脸部检测方法，设置参数
    global ratioAvg_sum, ratioAvg_num
    detector = FaceMeshDetector(maxFaces=1)  # 最多只检测一张脸
    previous_value = PreviousValue()
    # 人眼关键点所在的索引
    idList = [22, 23, 24, 26, 110, 130, 157, 158, 159, 160, 161, 243]  # 左眼

    # 接收实时绘图方法，图宽640高360，y轴刻度范围，间隔默认0.01，上下翻转默认False
    plotY = LivePlot(640, 360, [0, 1], interval=0.01, invert=True)

    ratioList = []  # 存放实时的人眼开合百分比

    blinkCounter = 0  # 眨眼计数器默认=0

    counter = 0  # 代表当前帧没计算过眨眼次数

    colorblink = (255, 0, 255)  # 不眨眼时计数器的颜色

    ratioAvg_sum = 0

    ratioAvg_num = 0

    start_time = time.time()
    s_time = time.time()
    # （3）图像处理
    while True:

        # 原视频较短，循环播放
        if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):  # 如果当前帧等于总帧数，即视频播放到了结尾
            cap.set(cv2.CAP_PROP_POS_FRAMES, 0)  # 让当前帧为0，重置视频从头开始

        # 返回帧图像是否读取成功success，读取的帧图像img
        success, img = cap.read()

        # （4）关键点检测，不绘制关键点及连线
        img, faces = detector.findFaceMesh(img, draw=False)  # img为绘制关键点后的图像，faces为关键点坐标

        # 如果检测到关键点了就接下去处理
        if faces:

            face = faces[0]  # face接收一张脸的所有关键点信息，且faces是三维列表
            # 遍历所有的眼部关键点
            for id in idList:
                cv2.circle(img, tuple(face[id]), 4, (0, 255, 255), cv2.FILLED)  # 以关键点为圆心半径为5画圆
            # 由于人脸距离摄像机的距离的远近会影响眼部上下边界之间的距离，因此通过开合比例来判断是否眨眼
            leftUp = tuple(face[159])  # 左眼最上边界的关键点坐标
            leftDown = tuple(face[23])  # 左眼最下边界的关键点坐标
            leftLeft = tuple(face[130])  # 左眼最左边界的关键点坐标
            leftRight = tuple(face[243])  # 左眼最右边界的关键点坐标
            # 计算上下边界以及左右边界的距离，函数返回距离length，以及线段两端点和中点坐标，这里用不到
            lengthver, __ = detector.findDistance(leftUp, leftDown)  # 上下边界距离
            lengthhor, __ = detector.findDistance(leftLeft, leftRight)  # 左右边界距离
            # 在上下及左右关键点之间各画一条线段，端点坐标是元组
            cv2.line(img, leftUp, leftDown, colorblink, 2)
            cv2.line(img, leftLeft, leftRight, colorblink, 2)
            # 计算竖直距离与水平距离的比值
            ratio = 100 * lengthver / lengthhor
            ratioList.append(ratio)  # 存放每帧图像的人眼开合比

            # 每10帧图像的人眼开合百分比取平均值，在图像上更新一个值
            if len(ratioList) > 10:
                ratioList.pop(0)  # 删除最前面一个元素
            ratioAvg = sum(ratioList) / len(ratioList)  # 超过10帧后，保证每10个元素取平均

            ratioAvg_sum = ratioAvg_sum + ratioAvg    # 每进行一次检测，开合比累加
            ratioAvg_num = ratioAvg_num + 1     # 检测次数
            ratioAvg_ave = ratioAvg_sum/ratioAvg_num    # 开合比平均值
            # 当开合比低于开合比平均值且已超过30帧时，认为是眨眼
            if ratioAvg < (ratioAvg_ave-4) and counter == 0:
                blinkCounter += 1  # 眨眼次数加1
                colorblink = (0, 255, 0)  # 眨眼时改变当前帧的颜色
                counter = 1  # 当前帧计算了一次眨眼

            # 保证在一次眨眼期间只在10帧中计算一次，不再多余的计算眨眼次数
            if counter != 0:  # 代表前面某10帧已经计算过一次眨眼
                counter += 1  # 累计不计算眨眼次数的帧数+1
                if counter > 10:  # 如果距前一次计算眨眼次数超过10帧了，那么下一次可以计算眨眼
                    counter = 0
                    colorblink = (255, 0, 255)  # 不眨眼时计数器颜色回到原颜色

            # 将眨眼计数显示在图上
            cvzone.putTextRect(img, f'BlinkCount:{blinkCounter}',  # 显示内容是字符串类型
                               (100, 50), 4, 3, colorR=colorblink)  # 设置文本显示位置、颜色、线条
            # 将眨眼频率显示在图上
            eyes_p = double(blinkCounter) / (time.time() - start_time)

            imgPlot = plotY.update(eyes_p, colorblink)  # 参数：每个时刻的y值，线条颜色

            cvzone.putTextRect(img, f'BlinkRate:{round(eyes_p,3)}',  # 显示内容是字符串类型
                               (50, 470), 4, 3, colorR=colorblink)

            # 当 3 秒前的眨眼频率比 3 秒后的眨眼频率差 0.05 时，判定 3 秒期间眨眼过快，将告警消息传递到可交互式页面进行展示
            if time.time() - s_time >= 3:
                s_time = time.time()
                if eyes_p > previous_value.get() + 0.07:
                    text.insert("insert", '\n' + '眨眼过快，心理波动较大')
                    if int(text.index('end').split('.')[0]) - 1 >= 9:
                        text.delete('1.0', tk.END)
                previous_value.update(eyes_p) #更新值

            # 重塑图像的宽和高，保证图像size和曲线图size一致
            img = cv2.resize(img, (600, 400))
        cv2.imshow('img', img)  # 传入窗口名和帧图像
        cv2.imshow('math', imgPlot)
        if cv2.waitKey(1) & int(cv2.getWindowProperty('img', cv2.WND_PROP_VISIBLE)) == 0:
            break
    # 释放视频资源
    cap.release()